package serverSocket;

import java.net.*;
import java.util.ArrayList;
import java.io.*;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import messages.AddBuddyMessage;
import messages.AwayMessage;
import messages.ChatMessage;
import messages.GetBuddyListMessage;
import messages.LoginMessage;
import messages.RemoveBuddyMessage;
import messages.ResultMessage;
import messages.ReturnBuddyListMessage;
import messages.ReturnStatusMessage;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import DBComm.Buddy;
import DBComm.BuddyList;
import DBComm.DBComm;
import DBComm.ReturnCodes;

import parser.Parser;

//import Server.DBComm;



public class ServerThread extends Thread
{
	private Socket socket = null;
	private String username = "Client";

	private boolean running = true;

	PrintWriter out;
	BufferedReader in;

	Parser messageParser = new Parser(); // XML message parser

	DBComm dbc = new DBComm();

	public ServerThread(Socket socket)
	{
		super("ServerThread");
		this.socket = socket;
	}


	private void setUserName(String username) {this.username = username;}
	public String getUserName() {return username;}

	public void run()
	{

		try
		{	
			dbc.updateStatus(getUserName(), 1);
			out = new PrintWriter(socket.getOutputStream(), true); // server socket output
			in = new BufferedReader(new InputStreamReader(socket.getInputStream())); // client socket input

			String outputLine = "";
			String inputLine = "";

			getShitThread getST = new getShitThread(getUserName());
			getST.start();

			while(running)
			{
				// check for data in the database and pipe it out to the client(s)
				int result = dbc.getWebEvent(getUserName());
				dbc.clearEvent(getUserName());

				/*
				ArrayList<ChatMessage> chatMessages = dbc.getMessage(getUserName());

				// send the messages out
				for(ChatMessage c : chatMessages)
				{
					// send the message directly to the client
					sendMessageDirectlyTo(c.getSender(), c.getMyReciever(), c.toXML());
				}
				 */


				/*
				// check to see if the client is present in the ServerThreadStaticHash
				// if the client is not there, it is a Web Client
				if(ServerThreadStaticHash.getServerThread(getUserName()) != null)
				{
					if(result == 1)
					{
						// get messages for the web client

						ArrayList<ChatMessage> chatMessages = dbc.getMessage(getUserName());

						// send the messages out
						for(ChatMessage c : chatMessages)
						{
							// send the message directly to the client
							sendMessageDirectlyTo(c.getSender(), c.getMyReciever(), c.toXML());
						}
					}
					else if(result == 2)
					{
						// get the buddy list for the web client
						BuddyList theBuddyList = dbc.getBuddyList(getUserName());

						ArrayList<Buddy> buddyArrayList = theBuddyList.getBuddyList();

						// send out the buddy list
						for(Buddy b : buddyArrayList)
						{
							//b.
						}
					}
					else if(result == 3)
					{
						// do both
						// get messages for the web client
						ArrayList<ChatMessage> chatMessages = dbc.getMessage(getUserName());

						// send the messages out
						for(ChatMessage c : chatMessages)
						{
							// send the message directly to the client
							sendMessageDirectlyTo(c.getSender(), c.getMyReciever(), c.toXML());
						}
					}
				}
				 */
				if((inputLine = in.readLine()) != null) // read lines in from the client input
				{
					// remove null-byte entries
					inputLine = inputLine.replaceAll("\0", "");

					// remove newline entries
					inputLine = inputLine.replaceAll("\n", "");

					// escape single-quote entries
					inputLine = inputLine.replaceAll("\'", "\\'");
					inputLine = inputLine.replaceAll("&apos;", "\\'");

					System.out.println(username + " sent a message: " + inputLine);
					if(inputLine.contains("\0")) {System.out.println("NULL BYTE SENT");}

					// check input
					if(inputLine.equals("Bye"))
					{
						System.out.println("Client connection terminated");
						socket.close();
						break;
					}
					else if(inputLine.startsWith("SETNAME:"))
					{
						// set the name
						inputLine = inputLine.replace("SETNAME:", "");

						System.out.println("\"" + username + "\" has changed his name to \"" + inputLine + "\"");
						setUserName(inputLine);

						// add this client to the HashMap
						ServerThreadStaticHash.addServerThread(inputLine, this);
					}
					else if(inputLine.startsWith("<message>") && inputLine.endsWith("</message>"))
					{
						// parse out the message
						parseMessage(inputLine);
						System.out.println(inputLine);
					}
					else
					{
						// process input and create an output response
						outputLine = inputLine; // echo back what the client sent
						out.println("Server: " + outputLine); // send the output back through the output socket
					}
				}

				/*
				chatMessages = dbc.getMessage(getUserName());

				// send the messages out
				for(ChatMessage c : chatMessages)
				{
					// send the message directly to the client
					sendMessageDirectlyTo(c.getSender(), c.getMyReciever(), c.toXML());
				}
				 */

			}

			out.close();
			in.close();
			//socket.close();
		}
		catch(SocketException e)
		{
			// often thrown when the Client is stopped without typing the "Bye" command
			System.out.println("Client (" + username + ") connection lost: SocketException - " + e.getMessage());
		}
		catch (Exception e) {
			e.printStackTrace();
		}
		dbc.updateStatus(getUserName(), 0);
	}

	private void parseMessage(String inputLine)
	{
		Document dom;

		//get the factory
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

		try {

			//Using factory get an instance of document builder
			DocumentBuilder db = dbf.newDocumentBuilder();

			//parse using builder to get DOM representation of the XML String
			dom = db.parse(new InputSource(new StringReader(inputLine)));

			// parse the document
			parseDocument(dom);


		}catch(ParserConfigurationException pce) {
			pce.printStackTrace();
		}catch(SAXException se) {
			se.printStackTrace();
		}catch(IOException ioe) {
			ioe.printStackTrace();
		}

	}

	private void parseDocument(Document dom)
	{
		//get the root element
		Element docEle = dom.getDocumentElement();

		// get the "type" tag to figure out the type of message
		String type = messageParser.getTextValue(docEle, "type");

		if(type.equals("Login")) // login message
		{
			LoginMessage l = messageParser.getLoginMessage(docEle);

			// verify the login
			int result = dbc.verifyLogin(l.getUsername(), l.getPassword());

			// create the response message
			ResultMessage r = new ResultMessage();
			r.addToDetails("login");

			// check the return code
			if(result == ReturnCodes.SUCCESS)
			{
				// successful login
				// add this client to the HashMap
				ServerThreadStaticHash.addServerThread(l.getUsername(), this);

				setUserName(l.getUsername());

				System.out.println("Client \"" + l.getUsername() + "\" has logged in");

				// set the response code
				r.setValue(ReturnCodes.SUCCESS);

				// mark the user as online
				dbc.updateStatus(l.getUsername(), 1);

				// send the response message
				sendMessageDirectlyTo(l.getUsername(),l.getUsername(),r.toXML());
				System.out.println(l.getUsername() + " removed from HashMap");
			}
			else if(result == ReturnCodes.BAD_PASSWORD)
			{
				// add this client to the HashMap
				ServerThreadStaticHash.addServerThread(l.getUsername(), this);

				// invalid password
				System.err.println("Client \"" + l.getUsername() + "\" supplied a bad password: " + l.getPassword());

				// set the response code
				r.setValue(ReturnCodes.BAD_PASSWORD);

				// send the response message
				sendMessageDirectlyTo(l.getUsername(),l.getUsername(),r.toXML());

				// remove this client from the HashMap
				ServerThreadStaticHash.removeServerThread(l.getUsername());
				System.out.println(l.getUsername() + " removed from HashMap");
			}
			else if(result == ReturnCodes.INVALID_LOGIN)
			{
				// add this client to the HashMap
				ServerThreadStaticHash.addServerThread(l.getUsername(), this);

				// invalid login (user does not exist)
				System.err.println("Client \"" + l.getUsername() + "\" supplied an invalid username: " + l.getUsername());

				// set the response code
				r.setValue(ReturnCodes.INVALID_LOGIN);

				// send the response message
				sendMessageDirectlyTo(l.getUsername(),l.getUsername(),r.toXML());

				// remove this client from the HashMap
				ServerThreadStaticHash.removeServerThread(l.getUsername());
			}
		}
		else if(type.equals("Chat")) // Chat message
		{
			ChatMessage c = messageParser.getChatMessage(docEle);

			// send the message directly to the recipient
			sendMessageDirectlyTo(c.getSender(), c.getMyReciever(), c.toXML());

			dbc.addMessage(c.getSender(), c.getMessage(), c.getMyReciever());
		}
		else if(type.equals("Away")) // Away message
		{
			AwayMessage away = messageParser.getAwayMessage(docEle);

			// send the message directly to the recipient
			sendMessageDirectlyTo(away.getSender(), away.getMyReciever(), away.toXML());

			dbc.addMessage(away.getSender(), away.getMessage(), away.getMyReciever());
		}
		else if(type.equals("AddBuddy")) // Chat message
		{
			AddBuddyMessage abm = messageParser.getAddBuddyMessage(docEle);

			// send the message directly to the recipient
			//sendMessageDirectlyTo(abm.getFrom(), abm.getTo(), "Text: " + abm.getFrom() + " Added Buddy - " + abm.getTo());
			dbc.addBuddy(abm.getFrom(), abm.getTo(), "Buddies");
		}
		else if(type.equals("RemoveBuddy")) // Chat message
		{
			RemoveBuddyMessage rbm = messageParser.getRemoveBuddyMessage(docEle);

			// send the message directly to the recipient
			//sendMessageDirectlyTo(rbm.getFrom(), rbm.getTo(), "Text: " + rbm.getFrom() + " Removed Buddy - " + rbm.getTo());
			dbc.removeBuddy(rbm.getFrom(), rbm.getTo(), "Buddies");
		}
		else if(type.equals("UpdateStatus")) // Chat message
		{
			ReturnStatusMessage rsm = messageParser.getReturnStatusMessage(docEle);

			// send the message directly to the recipient
			//			sendMessageDirectlyTo(rsm.getFrom(), rsm.getTo(), "Text: " + rsm.getFrom() + " Removed Buddy - " + rsm.getTo());
			dbc.updateStatus(rsm.getUsername(), rsm.getStatus());
		}
		else if(type.equals("GetBuddyList")) // Chat message
		{
			GetBuddyListMessage gmlm = messageParser.getBuddyListMessage(docEle);

			// send the message directly to the recipient
			//			sendMessageDirectlyTo(rsm.getFrom(), rsm.getTo(), "Text: " + rsm.getFrom() + " Removed Buddy - " + rsm.getTo());
			//			System.out.println(gmlm.getUsername());
			//sendMessage(gmlm.getUsername(), "Retrieving Buddy List: " + dbc.getBuddyList(gmlm.getUsername()).getBuddyList());
			BuddyList bl = dbc.getBuddyList(gmlm.getUsername());

			// construct the response message
			ReturnBuddyListMessage rblm = new ReturnBuddyListMessage(gmlm.getUsername());
			rblm.setBuddyList(bl.getBuddyList());

			// send the response message
			sendMessageDirectlyTo(rblm.getUsername(),rblm.getUsername(),rblm.toXML());
		}
	}

	// send a message directly through this thread's socket to the connected client
	public void sendMessage(String userFrom, String message)
	{
		try
		{
			//out.println(userFrom + ": " + message);
			out.println(message);

			if(message != null && !message.equals(""))
			{
				System.out.println("Message sent to " + this.getUserName() + "'s socket: " + message);
			}
		}
		catch(Exception e)
		{
			System.out.println("Exception in sendMessage: " + e.getClass().getSimpleName() + " - " + e.getMessage());
		}
	}

	// send a message from a given user (ServerThread) directly to a given user (ServerThread)
	// HashMap-based message re-routing
	public static void sendMessageDirectlyTo(String userFrom, String userTo, String message)
	{
		// get the HashMap entry for this client (based upon username)
		ServerThread theRecipient = ServerThreadStaticHash.getServerThread(userTo);

		if(theRecipient != null)
		{
			// send the message to the recipient's client socket
			theRecipient.sendMessage(userFrom, message);
		}
	}

	// send a message from a given user (ServerThread) directly to a given user (ServerThread)
	// Thread-based enumeration re-routing
	/*public void sendMessageDirectlyTo(String userFrom, String userTo, String message)
    {
    	// get a count of currently active threads in this ThreadGroup and create
    	// an empty array of the same size as the count
		int activeThreads = this.getThreadGroup().activeCount();
		Thread[] threadList = new Thread[activeThreads];

		// copy all currently active threads in this ThreadGroup into the array
		this.getThreadGroup().enumerate(threadList);

		ServerThread theClient = this; // get the current client by default
		for(Thread t : threadList) // look through the threads to find ServerThread classes
		{
			if(t instanceof ServerThread) // make sure we're looking at the clients (ServerThread classes)
			{
				ServerThread s = (ServerThread)t;

				if(s.getUserName().equals(userTo))
				{
					// found the desired recipient
					theClient = s;
					break;
				}
			}
		}

		if(theClient != null)
		{
			// pipe the message out to the client socket
			theClient.sendMessage(userFrom, message);
		}
    }*/

	public class getShitThread extends Thread{

		DBComm dbc;
		private String username = "";
		boolean alive;


		public getShitThread(String username){
			//System.out.println("Starting Web Listener.");
			dbc = new DBComm();

			this.username = username;
		}
		public void run(){
			ArrayList<ChatMessage> chatMessages;
			alive = true;
			while(alive){
				int result = dbc.getWebEvent(getUserName());
				chatMessages = dbc.getMessage(this.username);
				// send the messages out

				// send the message directly to the client
				if(result == 1)
				{
					// get messages for the web client

					// send the messages out
					for(ChatMessage c : chatMessages)
					{
						// send the message directly to the client
						sendMessageDirectlyTo(c.getSender(), c.getMyReciever(), c.toXML());
					}
				}
				else if(result == 2)
				{
					// get the buddy list for the web client
					BuddyList theBuddyList = dbc.getBuddyList(getUserName());

					ArrayList<Buddy> buddyArrayList = theBuddyList.getBuddyList();

					// send out the buddy list
					for(Buddy b : buddyArrayList)
					{
						//b.
					}
				}
				else if(result == 3)
				{
					// do both
					// get messages for the web client
					chatMessages = dbc.getMessage(getUserName());

					// send the messages out
					for(ChatMessage c : chatMessages)
					{
						// send the message directly to the client
						sendMessageDirectlyTo(c.getSender(), c.getMyReciever(), c.toXML());
					}
				}
				//System.out.println("checking web");

				try {
					Thread.sleep(200);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}

	}
}

